/*
 * (c) Copyright Ascensio System SIA 2010-2023
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

"use strict";

(function(window, undefined) {

	function CRasterHeapLineFree()
	{
		this.Y      = 0;
		this.Height = 0;
	}

	function CRasterDataInfo()
	{
		this.Chunk = null;
		this.Line = null;
		this.Index = 0;
	}

	function CRasterHeapLine()
	{
		this.Y = 0;
		this.Height = 0;
		this.Count = 0;
		this.CountBusy = 0;
		this.Images = null;
		this.Index = 0;
	}
	CRasterHeapLine.prototype.CreatePlaces = function(width, height, widthLine)
	{
		this.Height = height;
		this.Count = (widthLine / width) >> 0;

		var _size = this.Count;
		var arr = null;
		if (typeof(Int8Array) != 'undefined' && !window.opera)
			arr = new Int8Array(_size);
		else
			arr = new Array(_size);

		for (var i=0;i<_size;i++)
			arr[i] = 0;

		this.Images = arr;
	};

	CRasterHeapLine.prototype.Alloc = function()
	{
		if (this.Count == this.CountBusy)
			return -1;

		var arr = this.Images;
		if (arr[this.CountBusy] == 0)
		{
			arr[this.CountBusy] = 1;
			this.CountBusy += 1;
			return this.CountBusy - 1;
		}

		var _len = this.Count;
		for (var i = 0; i < _len; i++)
		{
			if (arr[i] == 0)
			{
				arr[i] = 1;
				this.CountBusy += 1;
				return i;
			}
		}
		return -1;
	};

	CRasterHeapLine.prototype.Free = function(index)
	{
		if (this.Images[index] == 1)
		{
			this.Images[index] = 0;
			this.CountBusy -= 1;
		}
		return this.CountBusy;
	};

	function CRasterHeapChuck()
	{
		this.CanvasImage    = null;
		this.CanvasCtx      = null;

		this.Width  = 0;
		this.Height = 0;

		this.LinesFree = [];
		this.LinesBusy = [];

		this.CurLine = null;

		this.FindOnlyEqualHeight = false;
	}
	CRasterHeapChuck.prototype.Create = function(width, height)
	{
		this.Width  = width;
		this.Height = height;

		this.CanvasImage = document.createElement('canvas');
		this.CanvasImage.width  = width;
		this.CanvasImage.height = height;

		this.CanvasCtx = this.CanvasImage.getContext('2d');
		this.CanvasCtx.globalCompositeOperation = "source-atop";

		var _freeLine = new CRasterHeapLineFree();
		_freeLine.Y         = 0;
		_freeLine.Height    = this.Height;
		this.LinesFree[0]   = _freeLine;
	};
	CRasterHeapChuck.prototype.Clear = function()
	{
		this.LinesBusy.splice(0, this.LinesBusy.length);
		this.LinesFree.splice(0, this.LinesFree.length);

		var _freeLine = new CRasterHeapLineFree();
		_freeLine.Y         = 0;
		_freeLine.Height    = this.Height;
		this.LinesFree[0]   = _freeLine;
	};
	CRasterHeapChuck.prototype.Alloc = function(width, height)
	{
		var _need_height = Math.max(width, height);
		var _busy_len = this.LinesBusy.length;
		for (var i = 0; i < _busy_len; i++)
		{
			var _line = this.LinesBusy[i];
			if (_line.Height >= _need_height)
			{
				var _index = _line.Alloc();
				if (-1 != _index)
				{
					var _ret = new CRasterDataInfo();
					_ret.Chunk = this;
					_ret.Line = _line;
					_ret.Index = _index;
					return _ret;
				}
			}
		}

		// линию не нашли. Начинаем искать из свободной памяти
		// ищем 3/2 от нужного размера. и параллельно 1
		var _need_height1 = (3 * _need_height) >> 1;
		if (this.FindOnlyEqualHeight)
			_need_height1 = _need_height;

		var _free_len = this.LinesFree.length;
		var _index_found_koef1 = -1;
		for (var i = 0; i < _free_len; i++)
		{
			var _line = this.LinesFree[i];
			if (_line.Height >= _need_height1)
			{
				// нашли
				var _new_line = new CRasterHeapLine();
				_new_line.CreatePlaces(_need_height1, _need_height1, this.Width);
				_new_line.Y = _line.Y;
				_new_line.Index = this.LinesBusy.length;
				this.LinesBusy.push(_new_line);

				_line.Y += _need_height1;
				_line.Height -= _need_height1;

				if (_line.Height == 0)
					this.LinesFree.splice(i, 1);

				var _ret = new CRasterDataInfo();
				_ret.Chunk = this;
				_ret.Line = _new_line;
				_ret.Index = _new_line.Alloc();
				return _ret;
			}
			else if (_line.Height >= _need_height && -1 == _index_found_koef1)
			{
				_index_found_koef1 = i;
			}
		}

		// 3/2 не нашли. если нашли для 1, то выделяем там
		if (-1 != _index_found_koef1)
		{
			var _line = this.LinesFree[_index_found_koef1];

			var _new_line = new CRasterHeapLine();
			_new_line.CreatePlaces(_need_height, _need_height, this.Width);
			_new_line.Y = _line.Y;
			_new_line.Index = this.LinesBusy.length;
			this.LinesBusy.push(_new_line);

			_line.Y += _need_height;
			_line.Height -= _need_height;

			if (_line.Height == 0)
				this.LinesFree.splice(i, 1);

			var _ret = new CRasterDataInfo();
			_ret.Chunk = this;
			_ret.Line = _new_line;
			_ret.Index = _new_line.Alloc();
			return _ret;
		}

		// не нашли.
		return null;
	};
	CRasterHeapChuck.prototype.Free = function(obj)
	{
		var _refs = obj.Line.Free(obj.Index);
		if (_refs == 0)
		{
			// нужно удалить линию и перебить всем оставшимся индексы

			var _line = obj.Line;
			this.LinesBusy.splice(_line.Index, 1);

			var _lines_busy = this.LinesBusy;
			var _busy_len = _lines_busy.length;
			for (var i = _line.Index; i < _busy_len; i++)
				_lines_busy[i].Index = i;

			// теперь нужно поправить linesfree
			var y1 = _line.Y;
			var y2 = _line.Y + _line.Height;
			var _lines_free = this.LinesFree;
			var _free_len = _lines_free.length;

			var _ind_prev = -1;
			var _ind_next = -1;
			for (var i = 0; i < _free_len; i++)
			{
				var _line_f = _lines_free[i];
				if (-1 == _ind_prev)
				{
					if (y1 == (_line_f.Y + _line_f.Height))
						_ind_prev = i;
				}
				else if (-1 == _ind_next)
				{
					if (y2 == _line_f.Y)
						_ind_next = i;
				}
				else
				{
					break;
				}
			}

			// нашли прилегаюую свободную память. теперь нужно их склеить, или создать новую
			if (-1 != _ind_prev && -1 != _ind_next)
			{
				_lines_free[_ind_prev].Height += (_line.Height + _lines_free[_ind_next].Height);
				_lines_free.splice(_ind_next, 1);
			}
			else if (-1 != _ind_prev)
			{
				_lines_free[_ind_prev].Height += _line.Height;
			}
			else if (-1 != _ind_next)
			{
				_lines_free[_ind_next].Y -= _line.Height;
				_lines_free[_ind_next].Height += _line.Height;
			}
			else
			{
				var _new_line = new CRasterHeapLineFree();
				_new_line.Y = _line.Y;
				_new_line.Height = _line.Height;
				_lines_free.push(_new_line);
			}
			_line = null;
		}
	};

	function CRasterHeapTotal(_size)
	{
		this.ChunkHeapSize = (undefined === _size) ? 3000 : _size; // 4 * 3000 * 3000 = 36Mb
		this.Chunks = [];
	}
	CRasterHeapTotal.prototype.Clear = function()
	{
		var _len = this.Chunks.length;
		for (var i = 0; i < _len; i++)
		{
			this.Chunks[i].Clear();
		}

		// теперь наверное удалим и память лишнюю
		if (_len > 1)
			this.Chunks.splice(1, _len - 1);
	};

	CRasterHeapTotal.prototype.Alloc = function(width, height)
	{
		var _len = this.Chunks.length;
		for (var i = 0; i < _len; i++)
		{
			var _ret = this.Chunks[i].Alloc(width, height);
			if (null != _ret)
				return _ret;
		}
		this.HeapAlloc(this.ChunkHeapSize, this.ChunkHeapSize);
		return this.Chunks[_len].Alloc(width, height);
	};

	CRasterHeapTotal.prototype.HeapAlloc = function(width, height)
	{
		var _chunk = new CRasterHeapChuck();
		_chunk.Create(width, height);
		this.Chunks[this.Chunks.length] = _chunk;
	};

	CRasterHeapTotal.prototype.CreateFirstChuck = function(_w, _h)
	{
		if (0 == this.Chunks.length)
		{
			this.Chunks[0] = new CRasterHeapChuck();
			this.Chunks[0].Create((undefined == _w) ? this.ChunkHeapSize : _w, (undefined == _h) ? this.ChunkHeapSize : _h);
		}
	};

	window['AscFonts'] = window['AscFonts'] || {};
	window['AscFonts'].CRasterHeapTotal = CRasterHeapTotal;

})(window, undefined);
